// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#pragma once
#include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"

namespace bt::hci {

// When communicating with the controller, we do so in terms of an
// AdvertisingHandle. This means that we frequently need to convert between a
// DeviceAddress and an AdvertisingHandle. AdvertisingHandleMap provides a 1:1
// bidirectional mapping between a DeviceAddress and an AdvertisingHandle,
// allocating the next available AdvertisingHandle to new DeviceAddresses.
//
// Users shouldn't rely on any particular ordering of the next available
// mapping. Any available AdvertisingHandle may be used.
//
// TODO(fxbug.dev/78081): implement a bidirectional map that can support looking
// up key to value as well as value to key (two map solution is probably good
// enough). Makes this clearer, easier to maintain, etc.
class AdvertisingHandleMap {
 public:
  // Instantiate an AdvertisingHandleMap. The capacity parameter specifies the
  // maximum number of mappings that this instance will support. Setting the
  // capacity also restricts the range of advertising handles
  // AdvertisingHandleMap will return: [0, capacity).
  explicit AdvertisingHandleMap(
      uint8_t capacity = hci_spec::kMaxAdvertisingHandle + 1)
      : capacity_(capacity) {}

  // Convert a DeviceAddress to an AdvertisingHandle, creating the mapping if it
  // doesn't already exist. The conversion may fail if there are already
  // hci_spec::kMaxAdvertisingHandles in the container.
  std::optional<hci_spec::AdvertisingHandle> MapHandle(
      const DeviceAddress& address);

  // Convert a DeviceAddress to an AdvertisingHandle. The conversion may fail if
  // there is no AdvertisingHandle currently mapping to the provided device
  // address.
  std::optional<hci_spec::AdvertisingHandle> GetHandle(
      const DeviceAddress& address) const;

  // Convert an AdvertisingHandle to a DeviceAddress. The conversion may fail if
  // there is no DeviceAddress currently mapping to the provided handle.
  std::optional<DeviceAddress> GetAddress(
      hci_spec::AdvertisingHandle handle) const;

  // Remove the mapping between an AdvertisingHandle and the DeviceAddress it
  // maps to. Immediate future calls to GetAddress(handle) with the same
  // AdvertisingHandle will fail because the mapping no longer exists. The
  // container may reuse the AdvertisingHandle for other DeviceAddresses in the
  // future. Immediate future calls to GetHandle(address) will result in a new
  // mapping with a new AdvertisingHandle.
  //
  // If the given handle doesn't map to any DeviceAddress, this function does
  // nothing.
  void RemoveHandle(hci_spec::AdvertisingHandle handle);

  // Remove the mapping between a DeviceAddress and the AdvertisingHandle it
  // maps to. Immediate future calls to GetAddress(handle) with the preivously
  // mapped AdvertisingHandle will fail because the mapping no longer exists.
  // The container may reuse the AdvertisingHandle for other DeviceAddresses in
  // the future. Immediate future calls to GetHandle(address) will result in a
  // new mapping with a new AdvertisingHandle.
  void RemoveAddress(const DeviceAddress& address);

  // Get the maximum number of mappings the AdvertisingHandleMap will support.
  uint8_t capacity() const { return capacity_; }

  // Retrieve the advertising handle that was most recently generated. This
  // function is primarily used by unit tests so as to avoid hardcoding values
  // or making assumptions about the starting point or ordering of advertising
  // handle generation.
  std::optional<hci_spec::AdvertisingHandle> LastUsedHandleForTesting() const;

  // Get the number of unique mappings in the container
  std::size_t Size() const;

  // Return true if the container has no mappings, false otherwise
  bool Empty() const;

  // Remove all mappings in the container
  void Clear();

 private:
  // Although not in the range of valid advertising handles (0x00 to 0xEF),
  // kStartHandle is chosen to be 0xFF because adding one to it will overflow to
  // 0, the first valid advertising handle.
  constexpr static hci_spec::AdvertisingHandle kStartHandle = 0xFF;

  // Tracks the maximum number of elements that can be stored in this container.
  //
  // NOTE: AdvertisingHandles have a range of [0, capacity_). This value isn't
  // set using default member initialization because it is set within the
  // constructor itself.
  uint8_t capacity_;

  // Generate the next valid, available, and within range AdvertisingHandle.
  // This function may fail if there are already
  // hci_spec::kMaxAdvertisingHandles in the container: there are no more valid
  // AdvertisingHandles.
  std::optional<hci_spec::AdvertisingHandle> NextHandle();

  // The last generated advertising handle used as a hint to generate the next
  // handle; defaults to kStartHandle if no handles have been generated yet.
  hci_spec::AdvertisingHandle last_handle_ = kStartHandle;

  std::unordered_map<DeviceAddress, hci_spec::AdvertisingHandle>
      addr_to_handle_;
  std::unordered_map<hci_spec::AdvertisingHandle, DeviceAddress>
      handle_to_addr_;
};

}  // namespace bt::hci
